#!/usr/bin/env python3

import tkinter as tk
from tkinter import messagebox
from tkinter import filedialog
import os
import struct
import yaml
import pysoem

class ConfigManagerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("EtherCAT Config Manager")
        self.root.geometry("1200x600")

        self.master = pysoem.Master()

        self.config_path = None
        self.config_files = None
        self.current_file = None
        self.modified = False

        self.config = None # yaml文件中的配置内容

        # 初始化 selected_file 作为 Tkinter StringVar
        self.selected_file = tk.StringVar()

        self.create_widgets()
        self.list_files()

    def detect_config_files(self):
        # 获取当前目录下所有 slave_x.yaml 文件
        files = [f for f in os.listdir(self.config_path) if f.startswith('slave_') and f.endswith('.yaml')]
        if not files:
            messagebox.showerror("Error", "No config files found in the selected directory.Please check the directory again.")
        return sorted(files)

    def create_widgets(self):
        self.count_label = tk.Label(self.root, text="Found Config Files: ")
        self.count_label.grid(row=1, column=0, sticky="w")

        # 单选按钮框架
        self.radio_frame = tk.Frame(self.root)
        self.radio_frame.grid(row=2, column=0, sticky="new")

        # 显示当前配置文件的路径
        self.path_label = tk.Label(self.root, text="No file selected", fg="blue")
        self.path_label.grid(row=0, column=1,  sticky="w")

        # 文本框和滚动条
        self.textbox_frame = tk.Frame(self.root)
        self.textbox_frame.grid(row=1, column=1, rowspan=3, sticky="nsew")

        self.scrollbar = tk.Scrollbar(self.textbox_frame)
        self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        self.textbox = tk.Text(self.textbox_frame, width=70, height=15, yscrollcommand=self.scrollbar.set)
        self.textbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        self.scrollbar.config(command=self.textbox.yview)

        # 绑定文本框的修改事件
        self.textbox.bind("<KeyRelease>", self.on_file_modified)

        # 按钮框架，放在左侧竖直排列
        self.button_frame = tk.Frame(self.root)
        # self.button_frame.pack(side=tk.LEFT, padx=10, pady=10)
        self.button_frame.grid(row=3, column=0,  pady=10,  sticky="sew")

        self.list_button = tk.Button(self.button_frame, text="Select Config File Directory", command=self.list_files)
        self.list_button.pack(fill=tk.X, pady=2)

        self.save_button = tk.Button(self.button_frame, text="Save File", command=self.save_file)
        self.save_button.pack(fill=tk.X, pady=2)

        self.write_button = tk.Button(self.button_frame, text="Write Config to Device", command=self.write_config)
        self.write_button.pack(fill=tk.X, pady=2)

        self.generate_button = tk.Button(self.button_frame, text="Generate SHM Config File", command=self.generate_config)
        self.generate_button.pack(fill=tk.X, pady=2)

        self.close_button = tk.Button(self.button_frame, text="Close", command=self.close_program)
        self.close_button.pack(fill=tk.X, pady=2)

        self.root.grid_rowconfigure(2, weight=1)
        self.root.grid_columnconfigure(1, weight=1)

    def list_files(self):
        for widget in self.radio_frame.winfo_children():
            widget.destroy()
        self.config_path = filedialog.askdirectory(initialdir="./config",title="Select Config File Directory")
        self.config_files = self.detect_config_files()
        if not self.config_files:
            return
        os.chdir(self.config_path)
        self.radio_buttons = []
        self.status_labels = []  # 用于存储每个slave的状态标志标签
        for idx, config_file in enumerate(self.config_files):
            button = tk.Radiobutton(self.radio_frame, text=f"Slave {idx + 1}", variable=self.selected_file,
                                    value=config_file, command=self.load_file)
            button.grid(row=idx, column=0, pady=2, sticky="w")
            self.radio_buttons.append(button)

            # 为每个单选按钮添加配置状态标签
            status_label = tk.Label(self.radio_frame, text="Not Configured", fg="red")
            status_label.grid(row=idx, column=1,pady=2, sticky="e")
            self.status_labels.append(status_label)

    def load_file(self):
        # 检查当前文件是否有修改，如果有则提示保存
        if self.modified:
            result = messagebox.askyesnocancel("Save Changes", "Do you want to save the changes?")
            if result:  # 如果选择保存
                self.save_file()
            elif result is None:  # 选择取消
                return

        # 加载新的配置文件
        file_name = self.selected_file.get()
        if file_name and file_name != self.current_file:
            with open(file_name, 'r') as file:
                content = file.read()
                self.textbox.delete(1.0, tk.END)
                self.textbox.insert(tk.END, content)
                self.current_file = file_name
                self.modified = False

            # 更新文件路径显示
            self.path_label.config(text=f"Current File: {os.path.abspath(file_name)}")

    def save_file(self):
        if self.current_file:
            with open(self.current_file, 'w') as file:
                content = self.textbox.get(1.0, tk.END).strip()
                file.write(content)
                self.modified = False
            messagebox.showinfo("Save", f"{self.current_file} saved successfully!")
        else:
            messagebox.showwarning("Warning", "No file selected!")

    def write_config(self):
        if self.current_file == None:
            messagebox.showwarning("Warning", "No file selected!")
            return

        with open(self.current_file, 'r') as file:
            content = file.read()
        self.config = yaml.safe_load(content)

        self.master.open("enp3s0")
        if(self.master.config_init() <= 0):
            self.master.close()
            messagebox.showerror("Error", "No slaves found!")
            return
        
        slave = self.master.slaves[self.config['Position']-1]
        if not ((slave.man == self.config["Vendor_ID"]) and
                (slave.id == self.config["Product_ID"])):
            self.master.close()
            messagebox.showerror("Error", "unexpected slave layout!")
            return
        
        slave.sdo_write(index=0x1C12, subindex=0, data=struct.pack("B", 0))
        slave.sdo_write(index=0x1C13, subindex=0, data=struct.pack("B", 0))
        
        
        slave.sdo_write(index=0x1600, subindex=0, data=struct.pack("B", 0))
        data = b''  # 清空data，空字节
        for idx, pdo in enumerate(self.config["RxPdo"]):
            data = struct.pack("BBH", pdo["BitLen"], pdo["SubIndex"], pdo["Index"])
            slave.sdo_write(index=0x1600, subindex=idx+1, data=data)
            print("RxPdo: subindex=%d data=%s"%(idx+1, data.hex()))
        slave.sdo_write(index=0x1600, subindex=0, data=struct.pack('B', len(self.config["RxPdo"])))
        print("RxPdo: 0x1600:0=%d"%(len(self.config["RxPdo"])))

        slave.sdo_write(index=0x1A00, subindex=0, data=struct.pack("B", 0))
        data = b''  # 清空data，空字节
        for idx, pdo in enumerate(self.config["TxPdo"]):
            data = struct.pack("BBH", pdo["BitLen"], pdo["SubIndex"], pdo["Index"])
            slave.sdo_write(index=0x1A00, subindex=idx+1, data=data)
            print("TxPdo: subindex=%d data=%s"%(idx+1, data.hex()))
        slave.sdo_write(index=0x1A00, subindex=0, data=struct.pack('B', len(self.config["TxPdo"])))
        print("TxPdo: 0x1A00:0=%d"%(len(self.config["TxPdo"])))

        data = b''  # 清空data，空字节
        data = struct.pack("BH", 1, 0x1600)
        slave.sdo_write(index=0x1C12, subindex=0, data=data)
        
        data = b''  # 清空data，空字节
        data = struct.pack("BH", 1, 0x1A00)
        slave.sdo_write(index=0x1C13, subindex=0, data=data)
        
        # for idx, sdo in enumerate(self.config["Sdo"]):
        #     slave.sdo_write(index=sdo["Index"], subindex=sdo["SubIndex"], data=sdo["Value"], ca=True)

        self.master.close()
        messagebox.showinfo("Write", "Configuration written to device!")

        # 更新对应slave的状态为已配置
        idx = self.config_files.index(self.current_file)
        self.status_labels[idx].config(text="Configured", fg="green")

    def generate_config(self):
        # 生成共享内存map配置文件
        rxpdo_file_name = "map_rxpdo"
        rxpdo_file = open(rxpdo_file_name, 'w')
        txpdo_file_name = "map_txpdo"
        txpdo_file = open(txpdo_file_name, 'w')
        shm_file_name = "map_shm"
        shm_file = open(shm_file_name, 'w')
        all_file_name = "map_all"
        all_file = open(all_file_name, 'w')
        # pdo_offset = 0
        
        for configfile in self.config_files:
            with open(configfile, 'r') as file:
                content = file.read()
            self.config = yaml.safe_load(content)
            for pdo in self.config["RxPdo"]:
                shm_file.write(f'{self.config["Name"]}{pdo["Variable"]}\n')
                rxpdo_file.write(f'0\t{self.config["Position"]}\t{self.config["Vendor_ID"]}\t{self.config["Product_ID"]}\t{pdo["Index"]}\t{pdo["SubIndex"]}\t{pdo["BitLen"]}\t{self.config["Name"]}{pdo["Variable"]}\n')
                all_file.write(f'0\t{self.config["Position"]}\t{self.config["Vendor_ID"]}\t{self.config["Product_ID"]}\t{pdo["Index"]}\t{pdo["SubIndex"]}\t{pdo["BitLen"]}\t{self.config["Name"]}{pdo["Variable"]}\n')
                # pdo_offset += pdo["BitLen"]
            for pdo in self.config["TxPdo"]:
                shm_file.write(f"{self.config['Name']}{pdo['Variable']}\n")
                txpdo_file.write(f'0\t{self.config["Position"]}\t{self.config["Vendor_ID"]}\t{self.config["Product_ID"]}\t{pdo["Index"]}\t{pdo["SubIndex"]}\t{pdo["BitLen"]}\t{self.config["Name"]}{pdo["Variable"]}\n')
                all_file.write(f'0\t{self.config["Position"]}\t{self.config["Vendor_ID"]}\t{self.config["Product_ID"]}\t{pdo["Index"]}\t{pdo["SubIndex"]}\t{pdo["BitLen"]}\t{self.config["Name"]}{pdo["Variable"]}\n')
                # pdo_offset += pdo["BitLen"]
        rxpdo_file.close()
        txpdo_file.close()
        all_file.close()
        messagebox.showinfo("Generate", "Memory map file: 'map_pdos' generated successfully!")
        
    def close_program(self):
        # 程序关闭时，检查是否保存未保存的更改
        if self.modified:
            result = messagebox.askyesnocancel("Save Changes", "You have unsaved changes. Do you want to save them?")
            if result:  # 如果选择保存
                self.save_file()
            elif result is None:  # 选择取消
                return

        self.root.quit()

    def on_file_modified(self, event=None):
        # 每当文本框内容修改时，设置modified为True
        self.modified = True
        # 更新对应slave的状态为未配置
        idx = self.config_files.index(self.current_file)
        self.status_labels[idx].config(text="Not Configured", fg="red")


if __name__ == "__main__":
    root = tk.Tk()
    app = ConfigManagerApp(root)
    root.protocol("WM_DELETE_WINDOW", app.close_program)
    root.mainloop()
